package redis.clients.jedis;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.util.IOUtils;


/**
 * DefaultJedisSocketFactory是Jedis中默认的Socket工厂类，
 * 用于创建与Redis服务器之间的连接。它实现了javax.net.SocketFactory接口，
 * 并提供了createSocket()方法来创建一个新的套接字。
 * <p>
 * 在创建新的套接字时，DefaultJedisSocketFactory会使用JedisProperties中的一些配置参数，
 * 如Host、Port等，来指定要连接的Redis服务器的地址和端口号。
 * 同时，它还支持SSL连接，可以通过设置useSSL属性为true来启用SSL加密传输。
 * DefaultJedisSocketFactory还提供了一些其他的配置选项，
 * 如SoTimeout、connectTimeout等，用于控制与Redis服务器之间的连接超时时间。
 * 这些选项可以在JedisProperties中进行配置，也可以通过代码直接设置。
 * <p>
 * 总之，DefaultJedisSocketFactory是Jedis中非常重要的一个组件，
 * 它负责创建和管理与Redis服务器之间的连接，确保Jedis能够正确地与Redis进行通信。
 */
public class DefaultJedisSocketFactory implements JedisSocketFactory {

    protected static final HostAndPort DEFAULT_HOST_AND_PORT = new HostAndPort(Protocol.DEFAULT_HOST,
            Protocol.DEFAULT_PORT);


    /*
     * DefaultJedisSocketFactory 的 hostAndPort 默认值为空字符串。
     * 这个配置项表示 Jedis 客户端连接 Redis 服务器时，要使用的主机名和端口号。
     *
     * 通过设置 hostAndPort 的值，可以指定客户端连接的 Redis 服务器的地址和端口号。
     * 如果未指定，则默认使用空字符串作为主机名和端口号，此时需要手动指定 Redis 服务器的地址和端口号。
     */
    private volatile HostAndPort hostAndPort = DEFAULT_HOST_AND_PORT;


    /*
     * DefaultJedisSocketFactory 的 connectionTimeout 默认值为2000毫秒（2秒）。
     * 这个配置项表示连接超时时间。当客户端尝试与 Redis 服务器建立连接时，
     * 如果超过了指定的超时时间（默认为2000毫秒，即2秒），则连接将被中断。
     * 这个配置项可以根据实际情况进行调整，以适应不同的网络环境和性能需求。
     */
    private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;


    /*
     * DefaultJedisSocketFactory 的 socketTimeout 默认值为2000毫秒（2秒）。
     * 这个配置项表示 socket 连接超时时间。当客户端与 Redis 服务器建立阻塞式 socket 连接时，
     * 如果超过了指定的超时时间（默认为2000毫秒，即2秒），则连接将被中断。
     * 这个配置项可以根据实际情况进行调整，以适应不同的网络环境和性能需求。
     */
    private int socketTimeout = Protocol.DEFAULT_TIMEOUT;
    private boolean ssl = false;
    private SSLSocketFactory sslSocketFactory = null;
    private SSLParameters sslParameters = null;
    private HostnameVerifier hostnameVerifier = null;
    private HostAndPortMapper hostAndPortMapper = null;

    public DefaultJedisSocketFactory() {
    }

    public DefaultJedisSocketFactory(HostAndPort hostAndPort) {
        this(hostAndPort, null);
    }

    public DefaultJedisSocketFactory(JedisClientConfig config) {
        this(null, config);
    }

    @Deprecated
    public DefaultJedisSocketFactory(String host, int port, int connectionTimeout, int socketTimeout,
                                     boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
                                     HostnameVerifier hostnameVerifier) {
        this.hostAndPort = new HostAndPort(host, port);
        this.connectionTimeout = connectionTimeout;
        this.socketTimeout = socketTimeout;
        this.ssl = ssl;
        this.sslSocketFactory = sslSocketFactory;
        this.sslParameters = sslParameters;
        this.hostnameVerifier = hostnameVerifier;
    }

    public DefaultJedisSocketFactory(HostAndPort hostAndPort, JedisClientConfig config) {
        if (hostAndPort != null) {
            this.hostAndPort = hostAndPort;
        }
        if (config != null) {
            this.connectionTimeout = config.getConnectionTimeoutMillis();
            this.socketTimeout = config.getSocketTimeoutMillis();
            this.ssl = config.isSsl();
            this.sslSocketFactory = config.getSslSocketFactory();
            this.sslParameters = config.getSslParameters();
            this.hostnameVerifier = config.getHostnameVerifier();
            this.hostAndPortMapper = config.getHostAndPortMapper();
        }
    }


    /**
     * JedisSocketFactory方法, 创建与redis的socket连接实例
     */
    @Override
    public Socket createSocket() throws JedisConnectionException {
        Socket socket = null;
        try {
            socket = new Socket();
            // ->@wjw_add
            socket.setReuseAddress(true);
            socket.setKeepAlive(true); // 将监视TCP连接是否有效
            socket.setTcpNoDelay(true); // 套接字缓冲区Whetherclosed，以确保数据的及时传递
            socket.setSoLinger(true, 0); //控件调用close（）方法，底层套接字立即关闭
            // <-@wjw_add

            // 开始创建socket连接
            HostAndPort hostAndPort = getSocketHostAndPort();
            // 设置host、port、连接超时时间
            socket.connect(new InetSocketAddress(hostAndPort.getHost(), hostAndPort.getPort()), getConnectionTimeout());
            // 设置 socket 连接超时时间
            socket.setSoTimeout(getSoTimeout());

            if (ssl) {
                SSLSocketFactory sslSocketFactory = getSslSocketFactory();
                if (null == sslSocketFactory) {
                    sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
                }
                socket = sslSocketFactory.createSocket(socket, hostAndPort.getHost(), hostAndPort.getPort(), true);

                SSLParameters sslParameters = getSslParameters();
                if (null != sslParameters) {
                    ((SSLSocket) socket).setSSLParameters(sslParameters);
                }

                HostnameVerifier hostnameVerifier = getHostnameVerifier();
                if (null != hostnameVerifier
                        && !hostnameVerifier.verify(hostAndPort.getHost(), ((SSLSocket) socket).getSession())) {
                    String message = String.format(
                            "The connection to '%s' failed ssl/tls hostname verification.", hostAndPort.getHost());
                    throw new JedisConnectionException(message);
                }
            }

            return socket;

        } catch (IOException ex) {

            IOUtils.closeQuietly(socket);

            throw new JedisConnectionException("Failed to create socket.", ex);
        }
    }


    /**
     * JedisSocketFactory方法,修改host和port
     */
    @Override
    public void updateHostAndPort(HostAndPort hostAndPort) {
        this.hostAndPort = hostAndPort;
    }


    public HostAndPort getSocketHostAndPort() {
        HostAndPortMapper mapper = getHostAndPortMapper();
        HostAndPort hostAndPort = getHostAndPort();
        if (mapper != null) {
            HostAndPort mapped = mapper.getHostAndPort(hostAndPort);
            if (mapped != null) {
                return mapped;
            }
        }
        return hostAndPort;
    }

    public HostAndPort getHostAndPort() {
        return this.hostAndPort;
    }

    /**
     * @param hostAndPort
     * @deprecated This will be removed in next major release. Use
     * {@link DefaultJedisSocketFactory#updateHostAndPort(redis.clients.jedis.HostAndPort)}.
     */
    @Deprecated
    public void setHostAndPort(HostAndPort hostAndPort) {
        this.hostAndPort = hostAndPort;
    }

    @Override
    public String getDescription() {
        return this.hostAndPort.toString();
    }

    @Override
    public String getHost() {
        return this.hostAndPort.getHost();
    }

    /**
     * @param host
     * @deprecated This will be removed in next major release. Use
     * {@link DefaultJedisSocketFactory#updateHostAndPort(redis.clients.jedis.HostAndPort)}.
     */
    @Override
    @Deprecated
    public void setHost(String host) {
        this.hostAndPort = new HostAndPort(host, this.hostAndPort.getPort());
    }

    @Override
    public int getPort() {
        return this.hostAndPort.getPort();
    }

    /**
     * @param port
     * @deprecated This will be removed in next major release. Use
     * {@link DefaultJedisSocketFactory#updateHostAndPort(redis.clients.jedis.HostAndPort)}.
     */
    @Override
    @Deprecated
    public void setPort(int port) {
        this.hostAndPort = new HostAndPort(this.hostAndPort.getHost(), port);
    }

    @Override
    public int getConnectionTimeout() {
        return this.connectionTimeout;
    }

    /**
     * @param connectionTimeout
     * @deprecated This will be removed in next major release.
     */
    @Override
    @Deprecated
    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    @Override
    public int getSoTimeout() {
        return this.socketTimeout;
    }

    /**
     * @param soTimeout
     * @deprecated This will be removed in next major release.
     */
    @Override
    @Deprecated
    public void setSoTimeout(int soTimeout) {
        this.socketTimeout = soTimeout;
    }

    public boolean isSsl() {
        return ssl;
    }

    /**
     * @param ssl
     * @deprecated This will be removed in next major release.
     */
    @Deprecated
    public void setSsl(boolean ssl) {
        this.ssl = ssl;
    }

    public SSLSocketFactory getSslSocketFactory() {
        return sslSocketFactory;
    }

    /**
     * @param sslSocketFactory
     * @deprecated This will be removed in next major release.
     */
    @Deprecated
    public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
        this.sslSocketFactory = sslSocketFactory;
    }

    public SSLParameters getSslParameters() {
        return sslParameters;
    }

    /**
     * @param sslParameters
     * @deprecated This will be removed in next major release.
     */
    @Deprecated
    public void setSslParameters(SSLParameters sslParameters) {
        this.sslParameters = sslParameters;
    }

    public HostnameVerifier getHostnameVerifier() {
        return hostnameVerifier;
    }

    /**
     * @param hostnameVerifier
     * @deprecated This will be removed in next major release.
     */
    @Deprecated
    public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
        this.hostnameVerifier = hostnameVerifier;
    }

    public HostAndPortMapper getHostAndPortMapper() {
        return hostAndPortMapper;
    }

    /**
     * @param hostAndPortMapper
     * @deprecated This will be removed in next major release.
     */
    @Deprecated
    public void setHostAndPortMapper(HostAndPortMapper hostAndPortMapper) {
        this.hostAndPortMapper = hostAndPortMapper;
    }

    @Override
    public String toString() {
        return "DefaultJedisSocketFactory{" + hostAndPort.toString() + "}";
    }
}
